﻿# Gameobjects.py: Player and Enemies classes
# Written by Victor Luna Freire - victorclf@live.com
# Copyright (c) Victor Luna Freire. All rights reserved.
# Sunday, May 26, 2009 00:00:00 PM
# Python 2.6.2

import math
import random
import warnings

import Maps
import Shared
import Defines
import Sprites
import Animation
import Renderer # view stuff
import Gamescript
import ScreenFX


class PointOfView:
    def __init__(self, pos=(0,0), angle=0):
        self.x = pos[0]
        self.y = pos[1]
        self.angle = angle

        
class Player(PointOfView):
    def __init__(self, pos=(0,0), angle=0, turn_speed=90, move_speed=768):
        PointOfView.__init__(self, pos, angle)
        self.angle_speed = turn_speed
        self.move_speed = move_speed
        self.xold = self.x
        self.yold = self.y
        
        self.max_health = 500
        self.health = self.max_health
                
        self.was_shot = False
        
    #HACK: limiting maximum per frame movement to avoid crossing walls in move_to
    def validate_position(self):
        ''' Checks if the position is valid. If not, reverts it to old values and returns False. '''
        gridposold = Renderer.togridF(self.xold, self.yold)
        gridpos = Renderer.togridF(self.x, self.y)
                        
        if (not Maps.Maps.is_valid_grid((gridpos))) or Maps.Maps.wall_onG(gridpos):
            # Invalid position, reverting to previous
            self.x = self.xold
            self.y = self.yold
            return False
                               
        return True # position was ok
    
    
    # Returns the distance to the victim if on sight. Else, -1.
    def on_sight(self, attacker):
        ray_angle = Renderer.angle_points(attacker.x, attacker.y, self.x, self.y)
        
        #Calculate wall distance and actors distance. If the actor distance is smaller, then we have a target!        
        #WALL DISTANCE
        h_hit = Renderer.horizontal_intersect(self.x, self.y, ray_angle, Maps.Maps.world_bounds[0], Maps.Maps.world_bounds[1], Maps.Maps.world)
        v_hit = Renderer.vertical_intersect(self.x, self.y, ray_angle, Maps.Maps.world_bounds[0], Maps.Maps.world_bounds[1], Maps.Maps.world)
        
        if h_hit[0] < 0 and v_hit[0] < 0: return -1
        
        is_v_closer, true_dist = Renderer.nearest_point_and_dist(self.x, self.y, ray_angle, h_hit[0], h_hit[1], v_hit[0], v_hit[1])
        closer_point = v_hit if is_v_closer else h_hit
        
        wall_dist = Renderer.correct_distance(self.x, self.y, self.angle, ray_angle, true_dist) # the correct distance
        
        #ACTORS DISTANCE
        actor_dist = Renderer.distance_points_ang(attacker.x, attacker.y, self.x, self.y, ray_angle)
        
        if actor_dist < wall_dist:
            return actor_dist #actor sighted!
        return -1
        
    
    def take_hit(self, damage):
        self.health -= damage
        self.was_shot = True
       
   
    def move_to_unchecked(self, angle, mult_factor):
        ''' Moves the player. '''
        self.xold = self.x
        self.yold = self.y
        self.x += self.move_speed * math.cos(math.radians(angle)) * mult_factor
        self.y -= self.move_speed * math.sin(math.radians(angle)) * mult_factor
        return True

    
    #HACK: LIMITING PLAYER POSITION TO CUBE_SIZE 
    def move_to(self, angle, mult_factor):
        ''' Moves the player. '''
        self.xold = self.x
        self.yold = self.y
        xvar = self.move_speed * math.cos(math.radians(angle)) * mult_factor
        yvar = self.move_speed * math.sin(math.radians(angle)) * mult_factor
        cubesize = Defines.CUBE_SIZE - 5
        if abs(xvar) > cubesize: xvar = cubesize if xvar > 0 else -cubesize
        if abs(yvar) > cubesize: yvar = cubesize if yvar > 0 else -cubesize
        self.x += xvar
        self.y -= yvar
        return self.validate_position()
                        

    def move_forward(self, mult_factor):
        ''' Moves the player forward. '''
        self.move_to(self.angle, mult_factor)
        
        
    def move_backward(self, mult_factor):
        ''' Moves the player backward. '''
        self.move_to(self.angle + 180, mult_factor)
        

    def turn_left(self, mult_factor):
        ''' Turns the angle of view to the left. '''
        self.angle += self.angle_speed * mult_factor
        

    def turn_right(self, mult_factor):
        ''' Turns the angle of view to the right. '''
        self.angle -= self.angle_speed * mult_factor
        

    def strafe_left(self, mult_factor):
        ''' Strafes the player to the left. '''
        self.move_to(self.angle + 90, mult_factor)
        

    def strafe_right(self, mult_factor):
        ''' Strafes the player to the right. '''
        self.move_to(self.angle - 90, mult_factor)



class Enemy(Player):
    def __init__(self, template='default', pos=(0,0), patrol_limits=None):
        '''
        Enemy:
        
        patrol_limits:
        - Area for patrolling. Tuple containing two grid positions(in unit coord) or None for static enemies.
        - The first grid point must be the top/left one in the range.
        
                
        '''

        Player.__init__(self, pos)
        
        # Sprite information
        self.y_adjust = 40
        self.fire_time_passed = 0
        
        self.load_template(template)
                
        if patrol_limits is None:
            self.state = 'idle'
        else:
            self.state = 'patrol'
            patrol_limits = (tuple(pos), tuple(patrol_limits))
            assert patrol_limits[0][0] == patrol_limits[1][0] or patrol_limits[0][1] == patrol_limits[1][1]
            self.patrol_limits = patrol_limits
            self.patrol_direction_vertical = True if patrol_limits[0][0] == patrol_limits[1][0] else False
            self.patrol_sense = True #True == towards second grid point
            
        self.surface_name = "enemies\\alien-test"
        self.still_active = True #dying, shooting, etc. it's doing something
        

    def load_template(self, template):
        default_path = "enemies"
        self.template = template
        if 0:
            pass
        else:
            if not template == 'soldier':
                self.template = 'soldier'
                print "Template -%s- not found. Loading default..." % template
            self.move_speed = 64
            self.turn_speed = 90
            self.damage = random.randint(9, 14)
            self.health = 50
            self.hit_chance = 80
            self.fire_time_delay = random.randint(300, 800)
            
            self.walk_frames = 4
            self.walk_time = [random.randint(280, 320) for i in xrange(self.walk_frames)]
            self.walk_anim = Animation.Animation((default_path, self.template, 'w'), (0, 0), self.walk_frames, self.walk_time, 'half-pass', True)
            self.walk_anim.play()
            self.walk_anim.current_frame = random.randint(0, 3)
            
            self.fire_frames = 4
            self.fire_time = [200] * self.fire_frames
            self.fire_time[1] = 400
            self.fire_anim = Animation.Animation((default_path, self.template, 'f'), (0, 0), self.fire_frames, self.fire_time, 'half-pass', False)
            
            self.death_frames = 8
            self.death_time = [100] * self.death_frames
            self.death_anim = Animation.Animation((default_path, self.template, 'd'), (0, 0), self.death_frames, self.death_time, 'half-pass', False)
                                              
                
    def main(self, player, mult_factor, time_variation):
        #TODO: CHANGE THIS FOR DEATH ANIMATION
        if not self.still_active: # is dead
            return False
        
        if self.health > 0:
            if self.was_shot:
                self.was_shot = False
                #do something, scream sound?
                pass
        else:
            #this enemy is gone!
            if self.was_shot:
                Gamescript.SoundDirector.sound_event('aliendeath')
                self.death_anim.play()
                self.was_shot = False
            self.death_anim.update()
            self.surface_name = self.death_anim.path
            self.still_active = self.death_anim.is_playing()
            return
        
        if not self.fire_anim.is_playing():
            if self.fire_time_passed < self.fire_time_delay: self.fire_time_passed += time_variation;
            self.walk_anim.update()
            self.surface_name = self.walk_anim.path
        else:
            self.fire_anim.update()
            self.surface_name = self.fire_anim.path
            return
            
        
        # LOOK FOR PLAYER
        if not self.fire_anim.is_playing() and not self.fire_time_passed < self.fire_time_delay:
            if not player.on_sight(self) < 0 and player.health > 0:
                Gamescript.SoundDirector.sound_event('alienlaser')
                if random.randint(0, 99) < self.hit_chance:
                    # hit
                    Gamescript.SoundDirector.sound_event('playerhit')
                    ScreenFX.ShieldHit.new_hit()
                    player.take_hit(self.damage)
                    self.fire_anim.play()
                    self.fire_time_passed = 0
                    self.surface_name = self.fire_anim.path
                    if player.health < 1:
                        Gamescript.SoundDirector.sound_event('playerdeath')
                else:
                    # missed
                    pass
                            
                        
        if self.state == 'patrol':
            if self.patrol_limits: # does this enemy patrol an area?
                # Vertical
                if self.patrol_direction_vertical:
                    if self.patrol_sense:
                        moveok = self.move_to(270, mult_factor) #moving South
                    else:
                        moveok = self.move_to(90, mult_factor) #moving North
                
                # Horizontal
                else:
                    if self.patrol_sense:
                        moveok = self.move_to(0, mult_factor) #moving East
                    else:
                        moveok = self.move_to(180, mult_factor) #moving West

                # Time to change direction?
                x_in_range = self.patrol_limits[0][0] <= self.x <= self.patrol_limits[1][0]
                y_in_range = self.patrol_limits[0][1] <= self.y <= self.patrol_limits[1][1]
                if not moveok or not(x_in_range and y_in_range):
                    if not y_in_range:
                        self.y = self.patrol_limits[1][1] if self.y > self.patrol_limits[1][1] else self.patrol_limits[0][1]
                    if not x_in_range:
                        self.x = self.patrol_limits[1][0] if self.x > self.patrol_limits[1][0] else self.patrol_limits[0][0]
                                      
                    self.patrol_sense = not(self.patrol_sense) #invert patrol_sense
            return
                            
                
#REMOVING SEARCH STATE DUE TO TIME CONSTRAINTS
#        elif self.state == 'search':
#            pass

        #elif self.state == 'fire':
        #    if random.randint(0, 99) < self.hit_chance:
        #        # hit
        #        player.health -= self.damage
        #        if not player.alive():
        #            self.state = 'patrol' # killed player
        #    else:
        #        # missed
        #        pass
        #
        #    if not self.player_on_sight(player):
        #        #self.state == 'search'
        #        self.state = 'patrol'
                
        elif self.state == 'idle':
            pass
        
        else:
            assert 0 # Impossible!
            
        
        return True

